/*
	SnowstormInput.cpp

	Contains code to handle user input and to forward input events

	Author: Aidan Goddard 25/7/14
*/

#include"Snowstorm.h"
#include<Window.h>
#include<Xinput.h>

#pragma comment(lib, "XINPUT9_1_0.lib")

#include<iostream>

/////////////////////////////////////////////
// data
static Snowstorm::Window *w;
extern ENGINE_VARS *ev;
static void(*KEY_PRESS_EVENT_HANDLERS[256])(void);
static void(*KEY_RELEASE_EVENT_HANDLERS[256])(void);
static void(*KEY_HELD_EVENT_HANDLERS[256])(void);
static void(*WINDOW_CLOSE_EVENT_HANDLER)(void);
static uint32_t KEY_STATES[256];
static int MOUSE_X, MOUSE_Y, MOUSE_Z; // relative number of pixels moved since last frame

/////////////////////////////////////////////
// window system input handlers

#define KEY_NEW_PRESS	0x80000000
#define KEY_NEW_RELEASE	0x40000000

static void key_pressed_event(Snowstorm::Window *w, uint8_t virtual_key)
{
	// mark the key as newly pressed
	KEY_STATES[virtual_key] = KEY_NEW_PRESS;
}

static void key_released_event(Snowstorm::Window *w, uint8_t virtual_key)
{
	// mark the key as newly released
	KEY_STATES[virtual_key] = KEY_NEW_RELEASE;
}

static void mouse_event(Snowstorm::Window *w, int mouse_x, int mouse_y, short wheel)
{
	// get values
	MOUSE_X += mouse_x;
	MOUSE_Y += mouse_y;
	MOUSE_Z += wheel;
}

static void mouse_move_event(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	v(screen_x) = lparam & 0xffff;
	v(screen_y) = (int)(lparam >> 16);
	return;
}

static void focus_lost_event(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// unlock the mouse cursor
	ClipCursor(NULL);

	// minimise the window
	if(v(minimize_on_lost_focus))
		w->Minimize();

	return;
}

static void focus_gained_event(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// lock the cursor to the screen
	if(v(lock_cursor_to_window))
	{
		POINT p;
		p.x = 0;
		p.y = 0;
		ClientToScreen(w->GetHWND(), &p);
		RECT rc;
		GetClientRect(w->GetHWND(), &rc);
		rc.left += p.x;
		rc.top += p.y;
		rc.right += p.x;
		rc.bottom += p.y;
		ClipCursor(&rc);
	}

	return;
}

static void window_set_cursor(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// check if cursor is in the window client area
	if(LOWORD(lparam) == HTCLIENT && v(hide_cursor))
		SetCursor(NULL);
	else
		SetCursor(LoadCursor(NULL, IDC_ARROW));
}

static void window_close_clicked(Snowstorm::Window *w, WPARAM wparam, LPARAM lparam)
{
	// call the installed handler if it exists
	if(WINDOW_CLOSE_EVENT_HANDLER)
		(*WINDOW_CLOSE_EVENT_HANDLER)();

	// otherwise, just exit
	else
		EngineStop();
}

/////////////////////////////////////////////
// register key event & unregister key event functions

void RegisterKeyPressEvent(uint8_t virtual_key, void(*event_handler)(void))
{
	// register the handler
	KEY_PRESS_EVENT_HANDLERS[virtual_key] = event_handler;

	// finished
	return;
}

void RegisterKeyReleaseEvent(uint8_t virtual_key, void(*event_handler)(void))
{
	// register the handler
	KEY_RELEASE_EVENT_HANDLERS[virtual_key] = event_handler;

	// finished
	return;
}

void RegisterKeyHeldEvent(uint8_t virtual_key, void(*event_handler)(void))
{
	// register the handler
	KEY_HELD_EVENT_HANDLERS[virtual_key] = event_handler;

	// finished
	return;
}

void RegisterWindowCloseEvent(void(*event_handler)(void))
{
	// register the handler
	WINDOW_CLOSE_EVENT_HANDLER = event_handler;
	return;
}

/////////////////////////////////////////////
// main init

void InitInput(Snowstorm::Window *wnd)
{
	// save window ptr
	w = wnd;

	// register the keyboard and mouse handlers
	wnd->RegisterKeyPressedEventHandler(key_pressed_event);
	wnd->RegisterKeyReleasedEventHandler(key_released_event);
	wnd->RegisterMouseEventHandler(mouse_event);
	wnd->RegisterHandler(WM_MOUSEMOVE, mouse_move_event);
	wnd->RegisterHandler(WM_SETFOCUS, focus_gained_event);
	wnd->RegisterHandler(WM_KILLFOCUS, focus_lost_event);
	wnd->RegisterHandler(WM_SETCURSOR, window_set_cursor);
	wnd->RegisterHandler(WM_CLOSE, window_close_clicked);

	// init the internal data
	for(int i = 0; i < 256; i++)
	{
		KEY_PRESS_EVENT_HANDLERS[i] = NULL;
		KEY_HELD_EVENT_HANDLERS[i] = NULL;
		KEY_RELEASE_EVENT_HANDLERS[i] = NULL;
		KEY_STATES[i] = 0;
	}
	
	// init mouse controls
	MOUSE_X = 0;
	MOUSE_Y = 0;
	MOUSE_Z = 0;

	// init the global keystates
	v(KEYSTATE[0]) = 0;
	v(KEYSTATE[1]) = 0;
	v(KEYSTATE[2]) = 0;
	v(KEYSTATE[3]) = 0;
	v(mickey_x) = 0;
	v(mickey_y) = 0;
	v(mickey_z) = 0;
	v(screen_x) = 0;
	v(screen_y) = 0;
	v(xbox_rx) = 0;
	v(xbox_ry) = 0;
	v(xbox_lx) = 0;
	v(xbox_ly) = 0;
	v(xbox_rtr) = 0;
	v(xbox_ltr) = 0;

	// call window focus gained event manually
	focus_gained_event(w, 0, 0);

	return;
}

////////////////////////////////////////////////
// the xbox 360 controller poll function & data
// see http://msdn.microsoft.com/en-gb/library/windows/desktop/ee417001(v=vs.85).aspx

static uint32_t controller_states[XUSER_MAX_COUNT];
static float xbox_deadzone_trigger = XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
static float xbox_deadzone_lp = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
static float xbox_deadzone_ln = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE*-1;
static float xbox_deadzone_rp = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
static float xbox_deadzone_rn = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE*-1;

static void CheckConnectedXboxControllers()
{
	// check each controller port
	for(int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		// state structure
		XINPUT_STATE state;
		ZeroMemory(&state, sizeof(XINPUT_STATE));

		// get the state of the controller
		DWORD result = XInputGetState(i, &state);

		// check state
		if(result == ERROR_SUCCESS)
		{
			// controller is connected
			controller_states[i] = 1;
		}
		else
		{
			// controller is not connected
			controller_states[i] = 0;
		}
	}
}

void XboxSetVibration(float left_motor, float right_motor)
{
	// create and fill vibration structure
	XINPUT_VIBRATION v;
	v.wLeftMotorSpeed = (WORD)(left_motor * 65535.0f);
	v.wRightMotorSpeed = (WORD)(right_motor * 65535.0f);

	// send it to each of the connected controllers
	for(int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		// check if the controller is connected
		if(controller_states[i])
		{
			// send the data
			XInputSetState(i, &v);
		}
	}
}

void XboxSetDeadZones(float trigger, float left_stick, float right_stick)
{
	// trigger
	if(trigger != XBOX_DEADZONE_DONT_CHANGE)
	{
		if(trigger == XBOX_DEADZONE_DEFAULT)
			xbox_deadzone_trigger = XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
		else
			xbox_deadzone_trigger = trigger / 0.00390625f;
	}

	// left stick
	if(left_stick != XBOX_DEADZONE_DONT_CHANGE)
	{
		if(left_stick == XBOX_DEADZONE_DEFAULT)
		{
			xbox_deadzone_lp = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
			xbox_deadzone_ln = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE*-1;
		}
		else
		{
			xbox_deadzone_lp = left_stick / 0.000030517578125f;
			xbox_deadzone_ln = left_stick / -0.000030517578125f;
		}
	}

	// right stick
	if(right_stick != XBOX_DEADZONE_DONT_CHANGE)
	{
		if(right_stick == XBOX_DEADZONE_DEFAULT)
		{
			xbox_deadzone_rp = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
			xbox_deadzone_rn = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE*-1;
		}
		else
		{
			xbox_deadzone_rp = right_stick / 0.000030517578125f;
			xbox_deadzone_rn = right_stick / -0.000030517578125f;
		}
	}
}

void GetXInputStates()
{
	// check for connected controllers every 5 seconds (ish)
	static float timer = 0;
	if(timer > 5)
	{
		CheckConnectedXboxControllers();
		timer = 0;
	}
	else
	{
		timer += v(frame_time);
	}

	// for each controller
	for(int i = 0; i < XUSER_MAX_COUNT; i++)
	{
		// check if controller is connected
		if(controller_states[i] > 0)
		{
			// get the controller state
			XINPUT_STATE controller_state;
			ZeroMemory(&controller_state, sizeof(XINPUT_STATE));
			DWORD result = XInputGetState(i, &controller_state);
			// check state
			if(result == ERROR_SUCCESS)
			{
				// controller is connected
				// check buttons
				// A button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_A)
					KEY_STATES[VK_XBOXA] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXA] = KEY_NEW_RELEASE;

				// B button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_B)
					KEY_STATES[VK_XBOXB] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXB] = KEY_NEW_RELEASE;

				// X button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_X)
					KEY_STATES[VK_XBOXX] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXX] = KEY_NEW_RELEASE;

				// Y button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_Y)
					KEY_STATES[VK_XBOXY] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXY] = KEY_NEW_RELEASE;

				// START button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_START)
					KEY_STATES[VK_XBOXSTART] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXSTART] = KEY_NEW_RELEASE;

				// BACK button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK)
					KEY_STATES[VK_XBOXBACK] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXBACK] = KEY_NEW_RELEASE;

				// LB button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)
					KEY_STATES[VK_XBOXLB] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXLB] = KEY_NEW_RELEASE;

				// RB button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)
					KEY_STATES[VK_XBOXRB] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXRB] = KEY_NEW_RELEASE;

				// LT button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB)
					KEY_STATES[VK_XBOXLT] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXLT] = KEY_NEW_RELEASE;

				// RT button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB)
					KEY_STATES[VK_XBOXRT] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXRT] = KEY_NEW_RELEASE;

				// DPAD UP button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)
					KEY_STATES[VK_XBOXDPADUP] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXDPADUP] = KEY_NEW_RELEASE;

				// DPAD DOWN button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
					KEY_STATES[VK_XBOXDPADDOWN] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXDPADDOWN] = KEY_NEW_RELEASE;

				// DPAD LEFT button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
					KEY_STATES[VK_XBOXDPADLEFT] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXDPADLEFT] = KEY_NEW_RELEASE;

				// DPAD RIGHT button
				if(controller_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
					KEY_STATES[VK_XBOXDPADRIGHT] = KEY_NEW_PRESS;
				else
					KEY_STATES[VK_XBOXDPADRIGHT] = KEY_NEW_RELEASE;

				// zero trigger states
				v(xbox_rx) = 0;
				v(xbox_ry) = 0;
				v(xbox_lx) = 0;
				v(xbox_ly) = 0;
				v(xbox_rtr) = 0;
				v(xbox_ltr) = 0;


				// get trigger states
				float ltr = controller_state.Gamepad.bLeftTrigger;
				float rtr = controller_state.Gamepad.bRightTrigger;
				if(ltr > xbox_deadzone_trigger)
					v(xbox_ltr) = ltr * 0.00390625f;
				if(rtr > xbox_deadzone_trigger)
					v(xbox_rtr) = rtr * 0.00390625f;


				// get left stick states
				float lx = controller_state.Gamepad.sThumbLX;
				float ly = controller_state.Gamepad.sThumbLY;
				if(lx > xbox_deadzone_lp || lx < xbox_deadzone_ln)
					v(xbox_lx) = lx * 0.000030517578125f;
				if(ly > xbox_deadzone_lp || ly < xbox_deadzone_ln)
					v(xbox_ly) = ly * 0.000030517578125f;
				

				// get right stick states
				float rx = controller_state.Gamepad.sThumbRX;
				float ry = controller_state.Gamepad.sThumbRY;
				if(rx > xbox_deadzone_rp || rx < xbox_deadzone_rn)
					v(xbox_rx) = rx * 0.000030517578125f;
				if(ry > xbox_deadzone_rp || ry < xbox_deadzone_rn)
					v(xbox_ry) = ry * 0.000030517578125f;

			}
			else
			{
				// controller is not connected
				controller_states[i] = 0;
			}
		}
	}
}

////////////////////////////////////////////////
// the function to update the keystates and to call any events

void InputUpdate()
{
	// check each key
	for(int i = 0; i < 256; i++)
	{
		// get pointer to key states value & the value itself
		uint32_t *key_state_ptr = &KEY_STATES[i];
		uint32_t key_state = *key_state_ptr;

		// get ptr to engine keystate qword and the bits position
		uint64_t *qword = &v(KEYSTATE[i >> 6]);
		uint64_t bits = 1ULL << (i & 0x3f);

		// check for fresh keypress condition
		if(key_state & KEY_NEW_PRESS)
		{
			*key_state_ptr = 1;
			*qword |= bits;
			void(*h)() = KEY_PRESS_EVENT_HANDLERS[i];
			if(h)
				(*h)();
		}

		// check for key still held condition
		if(key_state)
		{
			*key_state_ptr += 1;
			void(*h)() = KEY_HELD_EVENT_HANDLERS[i];
			if(h)
				(*h)();
		}

		// check for key release condition
		if(key_state & KEY_NEW_RELEASE)
		{
			*key_state_ptr = 0;
			*qword &= ~(bits);
			void(*h)() = KEY_RELEASE_EVENT_HANDLERS[i];
			if(h)
				(*h)();
		}
	}

	// mouse movement
	// get mickey values
	v(mickey_x) = MOUSE_X;
	v(mickey_y) = MOUSE_Y;
	v(mickey_z) = MOUSE_Z;

	// reset internal mouse coutners
	MOUSE_X = 0;
	MOUSE_Y = 0;
	MOUSE_Z = 0;

	// finished
	return;
}